// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "transpiler/yosys_cleartext_runner.h"

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "transpiler/data/cleartext_data.h"
#include "xls/common/status/matchers.h"
#include "xls/contrib/xlscc/metadata_output.pb.h"

using fully_homomorphic_encryption::transpiler::YosysRunner;

// Increments the argument char.
constexpr absl::string_view kNetlist = R"(

/* Generated by 0.10+12 */

module ifte(i, t, e, out);
  input [7:0] e;
  input i;
  output [7:0] out;
  input [7:0] t;
  imux2 _0_ ( .A(t[0]), .B(e[0]), .S(i), .Y(out[0]));
  imux2 _1_ ( .A(t[1]), .B(e[1]), .S(i), .Y(out[1]));
  imux2 _2_ ( .A(t[2]), .B(e[2]), .S(i), .Y(out[2]));
  imux2 _3_ ( .A(t[3]), .B(e[3]), .S(i), .Y(out[3]));
  imux2 _4_ ( .A(t[4]), .B(e[4]), .S(i), .Y(out[4]));
  imux2 _5_ ( .A(t[5]), .B(e[5]), .S(i), .Y(out[5]));
  imux2 _6_ ( .A(t[6]), .B(e[6]), .S(i), .Y(out[6]));
  imux2 _7_ ( .A(t[7]), .B(e[7]), .S(i), .Y(out[7]));
endmodule
)";

constexpr absl::string_view kMetadata = R"(
top_func_proto {
  name {
    name: "ifte"
    fully_qualified_name: "ifte"
    id: 22934049203632
  }
  return_type {
    as_int {
      width: 8
      is_signed: false
    }
  }
  params {
    name: "i"
    type {
      as_bool {
      }
    }
    is_reference: false
    is_const: false
  }
  params {
    name: "t"
    type {
      as_int {
        width: 8
        is_signed: false
      }
    }
    is_reference: false
    is_const: false
  }
  params {
    name: "e"
    type {
      as_int {
        width: 8
        is_signed: false
      }
    }
    is_reference: false
    is_const: false
  }
  whole_declaration_location {
    begin {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 18
      column: 1
    }
    end {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 22
      column: 1
    }
  }
  return_location {
    begin {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 18
      column: 1
    }
    end {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 18
      column: 10
    }
  }
  parameters_location {
    begin {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 18
      column: 20
    }
    end {
      filename: "third_party/fully_homomorphic_encryption/transpiler/examples/ifte/ifte.cc"
      line: 18
      column: 59
    }
  }
}
)";

constexpr absl::string_view kLiberty = R"liberty(
/********************************************/
/*                                          */
/* Supergate cell library for Bench marking */
/*                                          */
/* Symbiotic EDA GmbH / Moseley Instruments */
/* Niels A. Moseley                         */
/*                                          */
/* Process: none                            */
/*                                          */
/* Date   : 02-11-2018                      */
/* Version: 1.0                             */
/*                                          */
/********************************************/

library(supergate) {
  delay_model    : table_lookup;
  time_unit      : "1ns";

  /* Inverter */
  cell(inv) {
    area : 0;
    pin(A) {
      direction : input;
    }

    pin(Y) {
      direction : output;
      function : "A'";
      timing() {
        related_pin : "A";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("0.0");
        }
        cell_fall(scalar) {
            values("0.0");
        }
        rise_transition(scalar) {
            values("0.0");
        }
        fall_transition(scalar) {
            values("0.0");
        }
      }
    }
  }

  cell(buffer) {
    area : 0;
    pin(A) {
      direction : input;
    }
    pin(Y) {
      direction : output;
      function : "A";
      timing() {
        related_pin : "A";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("0.0");
        }
        cell_fall(scalar) {
            values("0.0");
        }
        rise_transition(scalar) {
            values("0.0");
        }
        fall_transition(scalar) {
            values("0.0");
        }
      }
    }
  }

  /* 2-input AND gate */
  cell(and2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A * B)";
      timing() {
        related_pin : "A B";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input NAND gate */
  cell(nand2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A * B)'";
      timing() {
        related_pin : "A B";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input OR gate */
  cell(or2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A + B)";
      timing() {
        related_pin : "A B";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input ANDYN gate */
  cell(andyn2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A * (B'))";
      timing() {
        related_pin : "A";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
      timing() {
        related_pin : "B";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input ANDNY gate */
  cell(andny2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "((A') * B)";
      timing() {
        related_pin : "A";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
      timing() {
        related_pin : "B";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input ORYN gate */
  cell(oryn2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A + (B'))";
      timing() {
        related_pin : "A";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
      timing() {
        related_pin : "B";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input ORNY gate */
  cell(orny2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "((A') + B)";
      timing() {
        related_pin : "A";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
      timing() {
        related_pin : "B";
        timing_sense : positive_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input NOR gate */
  cell(nor2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A + B)'";
      timing() {
        related_pin : "A B";
        timing_sense : negative_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input XOR */
  cell(xor2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A * (B')) + ((A') * B)";
      timing() {
        related_pin : "A B";
        timing_sense : non_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input XNOR */
  cell(xnor2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "((A * (B')) + ((A') * B))'";
      timing() {
        related_pin : "A B";
        timing_sense : non_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }

  /* 2-input MUX */
  cell(imux2) {
    area : 100;
    pin(A) {
      direction : input;
    }
    pin(B) {
      direction : input;
    }
    pin(S) {
      direction : input;
    }
    pin(Y) {
      direction: output;
      function : "(A * S) + (B * (S'))";
      timing() {
        related_pin : "A B S";
        timing_sense : non_unate;
        cell_rise(scalar) {
            values("1000.0");
        }
        cell_fall(scalar) {
            values("1000.0");
        }
        rise_transition(scalar) {
            values("1000.0");
        }
        fall_transition(scalar) {
            values("1000.0");
        }
      }
    }
  }
} /* end */
)liberty";

TEST(YosysRunnerTest, IfThenElseTrue) {
  auto _if = Encoded<bool>(true);
  auto _then = Encoded<char>('t');
  auto _else = Encoded<char>('e');
  Encoded<char> result;
  YosysRunner runner{std::string(kLiberty), std::string(kNetlist),
                     std::string(kMetadata)};
  XLS_ASSERT_OK(
      runner.Run(result.get(), {_if.get(), _then.get(), _else.get()}, {}));
  auto r = result.Decode();
  EXPECT_EQ(r, 't');
}

TEST(YosysRunnerTest, IfThenElseFalse) {
  auto _if = Encoded<bool>(false);
  auto _then = Encoded<char>('t');
  auto _else = Encoded<char>('e');
  Encoded<char> result;
  YosysRunner runner{std::string(kLiberty), std::string(kNetlist),
                     std::string(kMetadata)};
  XLS_ASSERT_OK(
      runner.Run(result.get(), {_if.get(), _then.get(), _else.get()}, {}));
  auto r = result.Decode();
  EXPECT_EQ(r, 'e');
}
